import math
from datetime import timezone
from typing import Any
from traceback import format_exc
from django.core.handlers.wsgi import WSGIRequest
from django.http import JsonResponse, HttpResponse
from django.core.paginator import Paginator
from django.db import transaction
from django.views import View

from rest_framework import status
from rest_framework.parsers import JSONParser
from jqgame.basegame import GameType
from jqgame.raffle import quanjushang_raffle

from warehouse.models import JuqiGood
from warehouse.serializers import JuqiGoodSerializer

from wuxianshang.models import (
    WuxianshangArgs,
    WuxianshangGame,
    WuxianshangGameGood,
    WuxianshangExtendBoss,
    WuxianshangExtendBossGood,
    create_boss_ticket,
    update_price_WuxianshangGame,
    wxs_raffle,
    get_wxs_args,
    WuxianshangRaffleRecord,
)

from .serializers import (
    WuxianshangGameSerializer,
    fill_WuxianshangGame,
    fill_WuxianshangRaffleRecord,
    WuxianshangArgsSerializer,
    WuxianshangRaffleRecordSerializer,
)

from wxuser.models import (
    UserHunJingRecord,
    UserPackage,
    UserPackageRecord,
    UserScoreRecord,
    UserStoredMoneyRecord,
    Users,
)
from wxuser.serializers import save_UserPackages


from juqiadmin.models import (
    Finance,
    get_game_finance,
    get_wxuser_finance,
    update_finnce_profit,
)
from juqiadmin.auth import login_auth, JuqiUser, check_admin

from jqexception.jqexecption import (
    BalanceInsufficientErr,
    ExtendBossGoodErr,
    ValueErr,
    UnknowErr,
    RaffleBusyErr,
    RightErr,
    NullErr,
)

from wxcloudrun.models import get_table_datetime

import logging

logger = logging.getLogger("log")


class GameView(View):
    def setup(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        super().setup(request, *args, **kwargs)

    @login_auth
    def get(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        #   req query params：
        #   - `many`  查询所有商品
        #   - `page` 分页
        #   - `num`  查询个数
        #   - `uuid`  good uuid
        #   - `name`  商品名称
        _list = request.GET.get("list", None)
        if not _list:
            # query single good
            game_uuid = request.GET.get("uuid", None)
            game_name = request.GET.get("name", None)
            state = request.GET.get("state", 1)

            g = None
            if game_uuid:
                g = (
                    WuxianshangGame.objects.filter(uuid=game_uuid)
                    .filter(deleted=False)
                    .filter(state=state)
                    .first()
                )
            elif game_name:
                g = (
                    WuxianshangGame.objects.filter(name=game_name)
                    .filter(deleted=False)
                    .filter(state=state)
                    .first()
                )
            if not g:
                raise NullErr
            g_s = WuxianshangGameSerializer(g)
            game = fill_WuxianshangGame(g_s.data)
            return JsonResponse(game, status=status.HTTP_200_OK)
        else:
            page = int(request.GET.get("page", None))
            num = min(50, int(request.GET.get("num", None)))

            ip_family = request.GET.get("ip_family", None)
            state = request.GET.get("state", 1)

            _objs = WuxianshangGame.objects

            if ip_family != None:
                _objs = _objs.filter(ip_family=ip_family)

            if state != None:
                _objs = _objs.filter(state=state)

            all_game = _objs.filter(deleted=False)

            p = Paginator(all_game, num)
            games = p.get_page(page).object_list
            detail_games = []
            for g in games:
                g_s = WuxianshangGameSerializer(g)
                try:
                    game = fill_WuxianshangGame(g_s.data)
                except:
                    logger.exception(
                        f"fill_WuxianshangGame game fail : {game.uuid} {format_exc()}"
                    )
                else:
                    detail_games.append(game)
            return JsonResponse(detail_games, status=status.HTTP_200_OK, safe=False)

    @login_auth
    @transaction.atomic
    def post(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        u = kwargs["user_info"]
        if u.type != "admin":
            raise RightErr

        WuxianshangGame.objects.select_for_update()
        WuxianshangGameGood.objects.select_for_update()

        game: dict = JSONParser().parse(request)

        goods = game.pop("goods")
        extend_boss = {}
        if game.get("extend_type_boss", 0) == 1:
            extend_boss = game.pop("extend_boss")

        game_s = WuxianshangGameSerializer(data=game)

        game_s.is_valid(raise_exception=True)
        game_s.save()
        game_uuid = game_s.data["uuid"]

        if game.get("extend_type_boss", 0) == 1:
            extend_boss["game_uuid"] = game_uuid
            extend_boss_goods = extend_boss.pop("goods")
            extendBoss = WuxianshangExtendBoss(**extend_boss)
            create_boss_ticket(extendBoss)
            extendBoss.save()
            extend_boss_goods_create = []

            not_global_good_count = 0
            for boss_good in extend_boss_goods:
                if not boss_good["is_global"]:
                    not_global_good_count += 1
                    if not_global_good_count > 1:
                        raise ExtendBossGoodErr
                    boss_good["count"] = extendBoss.draw_num

                boss_good["game_uuid"] = game_uuid
                boss_good["extend_boss_uuid"] = extendBoss.uuid
                extend_boss_goods_create.append(WuxianshangExtendBossGood(**boss_good))
            WuxianshangExtendBossGood.objects.bulk_create(extend_boss_goods_create)

        game_goods_create = []
        for good in goods:
            game_good = {
                "game_uuid": game_uuid,
                "good_uuid": good["good_uuid"],
                "good_weight": good["good_weight"],
                "good_rarity": good["good_rarity"],
            }
            game_goods_create.append(WuxianshangGameGood(**game_good))
        WuxianshangGameGood.objects.bulk_create(game_goods_create)
        _game = WuxianshangGame.objects.get(uuid=game_uuid)

        # update game price
        update_price_WuxianshangGame(_game)
        _game_s = WuxianshangGameSerializer(_game)
        detail_game_info = fill_WuxianshangGame(_game_s.data)
        return JsonResponse(detail_game_info, status=status.HTTP_201_CREATED)

    @login_auth
    @transaction.atomic
    def put(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        u = kwargs["user_info"]
        if u.type != "admin":
            raise RightErr

        WuxianshangGame.objects.select_for_update()

        body: dict = JSONParser().parse(request)
        g = (
            WuxianshangGame.objects.filter(uuid=body["uuid"])
            .filter(deleted=False)
            .first()
        )

        if not g:
            raise NullErr

        del body["uuid"]
        for k, v in body.items():
            setattr(g, k, v)
        g.clean()
        g.save()

        _game_s = WuxianshangGameSerializer(g)
        detail_game_info = fill_WuxianshangGame(_game_s.data)

        return JsonResponse(
            detail_game_info,
            status=status.HTTP_200_OK,
        )

    @login_auth
    @transaction.atomic
    def delete(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        u = kwargs["user_info"]
        if u.type != "admin":
            raise RightErr
        WuxianshangGame.objects.select_for_update()
        WuxianshangGameGood.objects.select_for_update()

        body: dict = JSONParser().parse(request)

        game = (
            WuxianshangGame.objects.filter(uuid=body["uuid"])
            .filter(deleted=False)
            .first()
        )

        if not game:
            raise NullErr

        WuxianshangGameGood.objects.filter(game_uuid=body["uuid"]).update(deleted=True)

        if game.extend_type_boss:
            WuxianshangExtendBoss.objects.filter(game_uuid=body["uuid"]).update(
                deleted=True
            )
            WuxianshangExtendBossGood.objects.filter(game_uuid=body["uuid"]).update(
                deleted=True
            )

        game.deleted = True
        game.save()

        return JsonResponse(
            {"code": 0, "data": f"del {game['uuid']}"}, status=status.HTTP_200_OK
        )

    @login_auth
    def head(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        update_time = get_table_datetime(WuxianshangGameGood._meta.db_table)
        timestamp = update_time.replace(tzinfo=timezone.utc).timestamp()
        return HttpResponse(
            status=status.HTTP_200_OK, headers={"Last-Modified": update_time}
        )


class RaffleView(View):
    def setup(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        super().setup(request, *args, **kwargs)

    @login_auth
    def get(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        _list = request.GET.get("list", None)
        if not _list:
            raise ValueError

        page = int(request.GET.get("page", None))
        num = min(50, int(request.GET.get("num", None)))
        game_uuid = request.GET.get("game_uuid", None)
        recoder_type = request.GET.get("recoder_type", 0)
        if not game_uuid:
            raise ValueError

        if recoder_type == 0:
            all_game = (
                WuxianshangRaffleRecord.objects.filter(deleted=False)
                .filter(game_uuid=game_uuid)
                .all()
            )

            p = Paginator(all_game, num)
            wrrs = p.get_page(page).object_list
            detail_wrrs = []

            for wrr in wrrs:
                try:
                    wrr_info = fill_WuxianshangRaffleRecord(wrr)
                except:
                    logger.exception(
                        f"fill_WuxianshangRaffleRecord game fail : {wrr.uuid} {format_exc()}"
                    )
                else:
                    detail_wrrs.append(wrr_info)
        else:
            all_cs = (
                WuxianshangGameGood.objects.filter(deleted=False)
                .filter(game_uuid=game_uuid)
                .filter(good_rarity="传说款")
                .all()
            )
            cs_goods = []
            for item in all_cs:
                cs_goods.append(item.good_uuid)

            all_game = (
                WuxianshangRaffleRecord.objects.filter(deleted=False)
                .filter(good_uuid__in=cs_goods)
                .filter(game_uuid=game_uuid)
                .all()
            )

            p = Paginator(all_game, num)
            wrrs = p.get_page(page).object_list
            detail_wrrs = []

            for wrr in wrrs:
                try:
                    wrr_info = fill_WuxianshangRaffleRecord(wrr)
                except:
                    logger.exception(
                        f"fill_WuxianshangRaffleRecord game fail : {wrr.uuid} {format_exc()}"
                    )
                else:
                    detail_wrrs.append(wrr_info)

        return JsonResponse(detail_wrrs, status=status.HTTP_200_OK, safe=False)

    @login_auth
    @transaction.atomic
    def post(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        def _boss_prize_exchange(game: WuxianshangGame):
            if not game.extend_type_boss:
                return

            boss_game = (
                WuxianshangExtendBoss.objects.filter(game_uuid=game.uuid)
                .filter(deleted=False)
                .select_for_update()
                .first()
            )
            if not boss_game:
                raise ValueErr
            _ticket_uuid = str(boss_game.ticket_uuid)
            _ticket_count = raffle_goods.get(_ticket_uuid, 0)

            if not _ticket_count:
                # 未抽中赏票, 不开奖
                return

            sum_ticket_count = boss_game.ticket_count + _ticket_count
            boss_game.ticket_count = sum_ticket_count
            boss_game.save()

            _draw_num = boss_game.draw_num

            boss_goods_count = {}
            each_boss_good = None
            good_map = {}

            def _draw_boss_game(sum_ticket_count, _draw_num, history_times):
                return (int(sum_ticket_count) // int(_draw_num)) > int(history_times)

            if _draw_boss_game(sum_ticket_count, _draw_num, boss_game.history_times):
                boss_goods = (
                    WuxianshangExtendBossGood.objects.filter(
                        extend_boss_uuid=boss_game.uuid
                    )
                    .filter(deleted=False)
                    .select_for_update()
                )

                global_boss_goods = boss_goods.filter(is_global=True)
                for g in global_boss_goods:
                    good_map[g.uuid] = g
                    boss_goods_count[g.uuid] = g.count

                each_boss_good = boss_goods.filter(is_global=False).first()
                good_map[each_boss_good.uuid] = each_boss_good
            else:
                # 赏票不足, 不开奖
                return

            while _draw_boss_game(sum_ticket_count, _draw_num, boss_game.history_times):
                # 一轮boss开奖
                boss_game.history_times += 1
                _cur_draw_num = _draw_num

                # each info:
                #   user: Users, good: JuqiGood, count: int
                boss_prize_info = []
                user_weight = {}

                ups = (
                    UserPackage.objects.filter(good_uuid=_ticket_uuid)
                    .filter(deleted=False)
                    .order_by("createdAt")
                    .select_for_update()
                )
                ups_update = []
                uprs_create = []
                for up in ups:
                    _consume_ticket_count = 0
                    if _cur_draw_num >= up.count:
                        _consume_ticket_count = up.count
                        _cur_draw_num -= _consume_ticket_count
                        up.count = 0
                        up.deleted = True
                    else:
                        _consume_ticket_count = _cur_draw_num
                        up.count -= _consume_ticket_count
                        _cur_draw_num = 0

                    # add user weight
                    user_weight[up.user_uuid] = _consume_ticket_count

                    ups_update.append(up)
                    upr_info = {
                        "user_uuid": up.user_uuid,
                        "good_uuid": _ticket_uuid,
                        "count": -_consume_ticket_count,
                        "detail": f"{boss_game.name} boss赏开奖: 自动消耗 {_consume_ticket_count} 个",
                    }
                    uprs_create.append(UserPackageRecord(**upr_info))
                    if _cur_draw_num == 0:
                        break
                UserPackage.objects.bulk_update(
                    ups_update, ["count", "deleted"], batch_size=1000
                )
                UserPackageRecord.objects.bulk_create(uprs_create, batch_size=1000)

                # give out non global prize
                finances_update = []

                user_map = {}
                user_finance_map = {}

                each_good_instance = JuqiGood.objects.get(
                    uuid=each_boss_good.good_uuid, deleted=False
                )
                # 更新总成本
                all_boss_game_cost = each_good_instance.price * _draw_num
                for user_uuid, ticket_count in user_weight.items():
                    _user = (
                        Users.objects.filter(uuid=user_uuid)
                        .filter(deleted=False)
                        .first()
                    )
                    if not _user:
                        raise ValueErr

                    user_map[user_uuid] = _user

                    # 更新平台从该用户获得的收入， 用户门票数 * 门票单价
                    user_finance = get_wxuser_finance(
                        user_uuid=_user.uuid,
                        gameType=GameType.wuxianshang,
                        game_uuid=game.uuid,
                    )
                    user_finance.all_raffle_income += (
                        user_weight[_user.uuid] * boss_game.ticket_price
                    )
                    # 更新每人都有的赏品成本
                    user_finance.all_raffle_cost += (
                        each_good_instance.price * user_weight[_user.uuid]
                    )
                    update_finnce_profit(user_finace)

                    user_finance_map[_user.uuid] = user_finance
                    finances_update.append(user_finance)

                    boss_prize_info.append([_user, each_good_instance, ticket_count])

                # raffle global prize
                once_raffle_res = quanjushang_raffle(
                    user_weight=user_weight, boss_goods_count=boss_goods_count
                )

                # once_raffle_res: {"good_uuid": {"user_uuid":count} }
                sum_ticket_price = boss_game.draw_num * boss_game.ticket_price

                for good_uuid in once_raffle_res:
                    _boss_good: WuxianshangExtendBossGood = good_map[good_uuid]
                    for user_uuid in once_raffle_res[good_uuid]:
                        _count = once_raffle_res[good_uuid][user_uuid]
                        _user: Users = user_map[user_uuid]

                        # 更新平台从该用户 付出的成本， 赏品*数量
                        _user_finance: Finance = user_finance_map[_user.uuid]

                        _good = JuqiGood.objects.get(
                            uuid=_boss_good.good_uuid, deleted=False
                        )
                        _cost = _good.price * _count
                        _user_finance.all_raffle_cost += _cost
                        all_boss_game_cost += _cost

                        update_finnce_profit(_user_finance)
                        # 背包保存信息
                        boss_prize_info.append([_user, _good, _count])

                save_UserPackages(
                    boss_prize_info,
                    game_type=GameType.wuxianshang_boss,
                    game=boss_game,
                )
                # todo: 计算boss 赏 finance利润
                # income = ticket_sum_count * ticket_price
                # cost = all_good_value
                wuxianshang_boss_finance = get_game_finance(GameType.wuxianshang_boss)
                wuxianshang_boss_finance.all_raffle_income += sum_ticket_price
                wuxianshang_boss_finance.all_raffle_cost += all_boss_game_cost
                update_finnce_profit(wuxianshang_boss_finance)

                finances_update.append(wuxianshang_boss_finance)
                Finance.objects.bulk_update(
                    finances_update,
                    ["all_raffle_income", "all_raffle_cost", "all_raffle_profit"],
                )

            # save models
            boss_game.save()

        u: JuqiUser = kwargs["user_info"]
        if u.type != "wxuser":
            raise RightErr

        openid = u.openid

        body: dict = JSONParser().parse(request)
        game_uuid = body.pop("game_uuid")
        pay_type = body.pop("pay_type")
        raffle_count = body.pop("raffle_count", 1)

        user = (
            Users.objects.filter(openid=openid)
            .filter(deleted=False)
            .select_for_update()
            .first()
        )
        if not user:
            raise NullErr

        game = (
            WuxianshangGame.objects.filter(uuid=game_uuid)
            .filter(deleted=False)
            .select_for_update()
            .first()
        )
        if not game:
            raise NullErr

        if game.state != 1:
            raise UnknowErr

        # used for pay
        wuxianshang_finance = get_game_finance(GameType.wuxianshang)
        sum_price = game.price * raffle_count

        currency = 0
        if pay_type == "stored_money":
            wuxianshang_finance.all_raffle_income += sum_price
            currency = user.stored_money
        elif pay_type == "hunjing":
            wuxianshang_finance.all_raffle_income += sum_price
            currency = user.hunjing
        elif pay_type == "score":
            currency = user.score
        else:
            raise ValueErr

        if currency < sum_price:
            raise BalanceInsufficientErr

        if pay_type == "stored_money":
            _old = user.stored_money
            _new = _old - sum_price
            user.stored_money = _new
            _record = {
                "user_uuid": user.uuid,
                "stored_money": -sum_price,
                "old_stored_money": _old,
                "new_stored_money": _new,
                "detail": f"消耗余额 {sum_price}: {game.name} 抽奖 {raffle_count} 次",
            }
            UserStoredMoneyRecord(**_record).save()
        elif pay_type == "hunjing":
            _old = user.hunjing
            _new = _old - sum_price
            user.hunjing = _new
            _record = {
                "user_uuid": user.uuid,
                "hunjing": -sum_price,
                "old_hunjing": _old,
                "new_hunjing": _new,
                "detail": f"消耗魂晶 {sum_price}: {game.name} 抽奖 {raffle_count} 次",
            }
            UserHunJingRecord(**_record).save()
        elif pay_type == "score":
            _old = user.score
            _new = _old - sum_price
            user.score = _new
            _record = {
                "user_uuid": user.uuid,
                "score": -sum_price,
                "old_score": _old,
                "new_score": _new,
                "detail": f"消耗积分 {sum_price}: {game.name} 抽奖 {raffle_count} 次",
            }
            UserScoreRecord(**_record).save()
        else:
            raise UnknowErr

        user_finace = get_wxuser_finance(
            user_uuid=user.uuid, gameType=GameType.wuxianshang, game_uuid=game.uuid
        )
        user_finace.all_raffle_income += sum_price

        # raffle
        sum_value = None
        res_goods = None
        packages_info = None
        next_wuxianshang_all_raffle_cost = None

        retry_raffle = 100
        for _ in range(retry_raffle):
            sum_value = 0
            res_goods = []
            packages_info = []

            raffle_goods = wxs_raffle(game=game, count=raffle_count)

            for k, count in raffle_goods.items():
                good = (
                    JuqiGood.objects.filter(uuid=k)
                    .filter(deleted=False)
                    .select_for_update()
                    .first()
                )
                if not good:
                    raise NullErr

                sum_value += count * good.price
                good_s = JuqiGoodSerializer(good)
                # good json data
                res_goods += [good_s.data for _ in range(count)]
                #
                packages_info.append([user, good, count])

            raffle_res = {}
            raffle_res["sum_value"] = sum_value
            raffle_res["goods"] = res_goods

            wxs_args = get_wxs_args()

            # 判断爆出产品后是否低于设定利润值
            next_wuxianshang_all_raffle_cost = (
                wuxianshang_finance.all_raffle_cost + sum_value
            )

            is_profit = next_wuxianshang_all_raffle_cost < math.ceil(
                wuxianshang_finance.all_raffle_income
                * (1.0 - float(wxs_args.wxs_min_sale_profit_percent))
            )
            if is_profit:
                break
        else:
            raise RaffleBusyErr

        game.count += raffle_count

        user_finace.all_raffle_cost += sum_value
        wuxianshang_finance.all_raffle_cost = next_wuxianshang_all_raffle_cost

        update_finnce_profit(user_finace)
        update_finnce_profit(wuxianshang_finance)
        # start save to package, deal finance, user money
        save_UserPackages(
            packages_info,
            game_type=GameType.wuxianshang,
            game=game,
        )
        Finance.objects.bulk_update(
            [wuxianshang_finance, user_finace],
            ["all_raffle_income", "all_raffle_cost", "all_raffle_profit"],
        )
        user.save()
        game.save()
        _boss_prize_exchange(game)

        return JsonResponse(
            raffle_res,
            status=status.HTTP_200_OK,
            json_dumps_params={"ensure_ascii": False},
        )


# /api/wuxianshang/wuxianshangargs
class WuxianshangArgsView(View):
    def setup(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        super().setup(request, *args, **kwargs)

    @check_admin
    def get(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        #   req query params：
        #   - `many`  查询所有商品

        wxsa = get_wxs_args()
        wxsa_s = WuxianshangArgsSerializer(wxsa)
        return JsonResponse(wxsa_s.data, status=status.HTTP_200_OK, safe=False)

    @check_admin
    @transaction.atomic
    def put(self, request: WSGIRequest, *args: Any, **kwargs: Any):
        WuxianshangArgs.objects.filter(id=1).select_for_update()
        body = JSONParser().parse(request)
        wxsa = get_wxs_args()

        wxsa_s = WuxianshangArgsSerializer(wxsa, data=body)
        wxsa_s.is_valid(raise_exception=True)
        wxsa_s.save()

        return JsonResponse(wxsa_s.data, status=status.HTTP_200_OK, safe=False)
